// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package weavertest

import (
	"context"
	"fmt"
	"os"
	"reflect"
	"regexp"
	"strings"
	"testing"

	"github.com/ServiceWeaver/weaver/internal/weaver"
	"github.com/ServiceWeaver/weaver/runtime"
	"github.com/ServiceWeaver/weaver/runtime/codegen"
	"github.com/ServiceWeaver/weaver/runtime/protos"
	"github.com/google/uuid"
)

const matchNothingRE = "a^" // Regular expression that never matches

// initMultiProcess initializes a brand new multi-process execution environment
// that places every component in its own collocation group. It returns a
// function that can be used to stop the execution.
//
// locals contains components that should be co-located with the main component
// and not replicated.
//
// logWriter is used to handle log entries generated by the execution.
//
// Future extension: allow options so the user can control collocation/replication/etc.
func initMultiProcess(ctx context.Context, t testing.TB, isBench bool, runner Runner, locals []reflect.Type, logWriter func(*protos.LogEntry)) (runtime.Bootstrap, func() error, error) {
	t.Helper()
	bootstrap, err := runtime.GetBootstrap(ctx)
	if err != nil {
		return runtime.Bootstrap{}, nil, err
	}
	if bootstrap.HasPipes() {
		// This is a child process, so just start the application and wait.
		defer func() {
			if r := recover(); r != nil {
				fmt.Fprintf(os.Stderr, "panic in Service Weaver sub-process: %v\n", r)
			} else {
				fmt.Fprintf(os.Stderr, "Service Weaver sub-process exiting\n")
			}
			os.Exit(1)
		}()

		opts := weaver.RemoteWeaveletOptions{}
		wlet, err := weaver.NewRemoteWeavelet(ctx, codegen.Registered(), bootstrap, opts)
		if err != nil {
			panic(err)
		}
		return runtime.Bootstrap{}, nil, wlet.Wait()
	}

	// Construct AppConfig and EnvelopeInfo.
	appConfig := &protos.AppConfig{}
	if runner.Config != "" {
		var err error
		appConfig, err = runtime.ParseConfig("[testconfig]", runner.Config, codegen.ComponentConfigValidator)
		if err != nil {
			return runtime.Bootstrap{}, nil, err
		}
	}
	exe, err := os.Executable()
	if err != nil {
		return runtime.Bootstrap{}, nil, fmt.Errorf("error fetching binary path: %v", err)
	}
	name := t.Name()
	appConfig.Name = strings.ReplaceAll(name, "/", "_")
	appConfig.Binary = exe
	nameRE := "^" + regexp.QuoteMeta(name) + "$"
	if isBench {
		appConfig.Args = []string{"-test.run", matchNothingRE, "-test.bench", nameRE}
	} else {
		appConfig.Args = []string{"-test.run", nameRE}
	}

	wlet := &protos.EnvelopeInfo{
		App:             appConfig.Name,
		DeploymentId:    uuid.New().String(),
		Sections:        appConfig.Sections,
		InternalAddress: "localhost:0",
	}

	// Launch the deployer.
	d := newDeployer(ctx, wlet, appConfig, runner, locals, logWriter)
	bootstrap, err = d.start()
	if err != nil {
		return runtime.Bootstrap{}, nil, err
	}
	return bootstrap, d.cleanup, nil
}
